﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;
using System.Drawing;

using Vitruvian.Serialization;
using Vitruvian.Serialization.Xml;
using Vitruvian.Serialization.Formatters;
using Vitruvian.Logging;
using Vitruvian.Testing;

namespace SpotItComponents
{
    [ContainsTests("Components.SymbolStuff")]
    [OptimisticSerialization]
    public class SymbolSet : Component
    {
        #region Private Data Members
        private static Logger logger = Logger.GetLogger(typeof(SymbolSet));
        private string label;
        #endregion

        #region Public Properties
        public bool IsPrivate { get; set; }
        public Guid Owner { get; set; }
        public List<Category> Categories { get; set; }
        public List<Symbol> Symbols { get; set; }
        #endregion

        #region Constructors
        public SymbolSet()
        {
            Initialize(null, false);
        }

        public SymbolSet(string label, bool isPrivate)
        {
            Initialize(label, isPrivate);
        }

        public SymbolSet(SymbolSet ss, bool isPrivate)
        {
            Initialize(ss.Label, ss.IsPrivate);
            AddCategoriesFrom(ss.Categories);
            AddSymbolsFrom(ss.Symbols);
        }
        #endregion

        #region Public Properties and Methods

        public string Label
        {
            get { return label; }
            set { SetLabel(value) ; }
        }

        public List<Symbol> SelectedSymbols
        {
            get
            {
                List<Symbol> result = new List<Symbol>();
                foreach (Symbol s in Symbols)
                    if (s.Selected)
                        result.Add(s);
                return result;
            }
        }

        public int NumberOfSelectedSymbols
        {
            get
            {
                int result = 0;
                foreach (Symbol s in Symbols)
                    if (s.Selected)
                        result++;
                return result;
            }
        }

        public void AddCategory(Category category)
        {
            if (category!=null)
                this.Categories.Add(category);
        }

        public void AddCategoriesFrom(List<Category> categoryList)
        {
            if (categoryList!=null)
                foreach (Category c in categoryList)
                    this.Categories.Add(c);
        }

        public void RemoveCategory(Category category)
        {
            if (category != null)
                Categories.Remove(category);
        }

        public void RemoveAllCategories()
        {
            Categories.Clear();
        }

        public void AddSymbol(Symbol symbol)
        {
            if (symbol != null)
                this.Symbols.Add(Symbol.CopySymbolIntoSet(symbol, this));
        }

        public void AddSymbolsFrom(List<Symbol> symbolList)
        {
            if (symbolList!=null)
                foreach (Symbol s in symbolList)
                    this.Symbols.Add(Symbol.CopySymbolIntoSet(s, this));
        }

        public void RemoveSymbol(Symbol symbol)
        {
            if (symbol != null)
                Symbols.Remove(symbol);
        }

        public void RemoveAllSymbols()
        {
            Symbols.Clear();
        }

        public void Save(string fileName)
        {
            if (!string.IsNullOrEmpty(fileName))
                XmlFramework.Serialize(fileName, this);
            else
                throw new ApplicationException("The containing symbol set does not have a valid file name");
        }

        public void Load(string fileName)
        {
            if (!string.IsNullOrEmpty(fileName))
                XmlFramework.Deserialize(fileName, this);
            else
                throw new ApplicationException("The containing symbol set does not have a valid file name");
        }

        public void ClearSelectedSymbols()
        {
            foreach (Symbol s in Symbols)
                s.Selected = false;
        }

        /// <summary>
        /// RamdonlySelectSymbols
        /// 
        /// This method first clears the current selections and then randomly marks a specified number of symbols in the set as selected.
        /// </summary>
        /// <param name="numberOfSymbols">Number of symbols to select</param>
        /// <returns>True if successful, False if not</returns>
        public bool RandomlySelectSymbols(int numberOfSymbols)
        {
            bool result = false;
            ClearSelectedSymbols();
            if (numberOfSymbols > 0 && numberOfSymbols <= Symbols.Count)
            {
                List<IndexRandomNumberPair> randList = new List<IndexRandomNumberPair>(Symbols.Count);
                for (int i = 0; i < Symbols.Count; i++)
                    randList.Add(new IndexRandomNumberPair(i));
                randList.Sort(CompareIndexRandomNumberPairs);
                for (int j = 0; j < numberOfSymbols; j++)
                {
                    Symbols[randList[j].Index].Selected = true;
                }

            }
            return result;
        }
        #endregion

        #region Private Methods
        private void Initialize(string label, bool isPrivate)
        {
            Id = Guid.NewGuid();
            SetLabel(label);
            IsPrivate = isPrivate;
            Categories = new List<Category>();
            Symbols = new List<Symbol>();
        }

        private void SetLabel(string newLabel)
        {
            label = (!string.IsNullOrEmpty(newLabel)) ? newLabel : "Unnamed Symbol Set";
        }

        private static int CompareIndexRandomNumberPairs(IndexRandomNumberPair p1, IndexRandomNumberPair p2)
        {
            return p1.CompareTo(p2);
        }

        private class IndexRandomNumberPair : IComparable
        {
            private static Random randomizer = new Random(DateTime.Now.Millisecond);
            private int index;
            private int randonNumber;

            public IndexRandomNumberPair(int index)
            {
                this.index = index;
                this.randonNumber = randomizer.Next(0, Int32.MaxValue-1);
            }

            public int Index { get { return index; } }

            #region IComparable Members

            public int CompareTo(object obj)
            {
                int result = 0;
                if (obj is IndexRandomNumberPair)
                {
                    IndexRandomNumberPair other = (IndexRandomNumberPair) obj;
                    result = this.randonNumber.CompareTo(other.randonNumber);
                }
                else
                    throw new ArgumentException("object is not a IndexRandomNumberPair for CompareTo method");
                return result;
            }

            #endregion
        }
        #endregion

        #region Unit Test Cases
#if (DEBUG)
        [SetupTest]
        private void Test_Setup()
        {
        }

        [Test(1)]
        private void Test_SymbolSet_Constructors_Initialize_Properties_and_Compare()
        {
            SymbolSet ss1 = new SymbolSet();
            Assert.IsNotEqual(Guid.Empty, ss1.Id);
            Assert.IsEqual("Unnamed Symbol Set", ss1.Label);
            Assert.IsFalse(ss1.IsPrivate);
            Assert.IsEqual(Guid.Empty, ss1.Owner);
            Assert.IsNotNull(ss1.Categories);
            Assert.IsEqual(0, ss1.Categories.Count);
            Assert.IsNotNull(ss1.Symbols);
            Assert.IsEqual(0, ss1.Symbols.Count);

            SymbolSet ss2 = new SymbolSet("My Favorite Symbols", true);
            Assert.IsNotEqual(Guid.Empty, ss2.Id);
            Assert.IsEqual("My Favorite Symbols", ss2.Label);
            Assert.IsTrue(ss2.IsPrivate);
            Assert.IsEqual(Guid.Empty, ss2.Owner);
            Assert.IsNotNull(ss2.Categories);
            Assert.IsEqual(0, ss2.Categories.Count);
            Assert.IsNotNull(ss2.Symbols);
            Assert.IsEqual(0, ss2.Symbols.Count);

            SymbolSet ss3 = new SymbolSet("", false);
            Assert.IsNotEqual(Guid.Empty, ss3.Id);
            Assert.IsEqual("Unnamed Symbol Set", ss3.Label);
            Assert.IsFalse(ss3.IsPrivate);
            Assert.IsEqual(Guid.Empty, ss3.Owner);
            Assert.IsNotNull(ss3.Categories);
            Assert.IsEqual(0, ss3.Categories.Count);
            Assert.IsNotNull(ss3.Symbols);
            Assert.IsEqual(0, ss3.Symbols.Count);

            SymbolSet ss4 = new SymbolSet(ss2, false);
            Assert.IsNotEqual(Guid.Empty, ss4.Id);
            Assert.IsNotEqual(ss2.Id, ss4.Id);
            Assert.IsEqual("My Favorite Symbols", ss4.Label);
            Assert.IsTrue(ss4.IsPrivate);
            Assert.IsEqual(Guid.Empty, ss4.Owner);
            Assert.IsNotNull(ss4.Categories);
            Assert.IsEqual(0, ss4.Categories.Count);
            Assert.IsNotNull(ss4.Symbols);
            Assert.IsEqual(0, ss4.Symbols.Count);

            Assert.IsEqual(0, ss1.CompareTo(ss1));
            Assert.IsNotEqual(0, ss1.CompareTo(ss4));
        }

        [Test(3)]
        public void Test_SymbolSet_Category()
        {
            SymbolSet ss1 = new SymbolSet("My Favorite Symbols", true);
            Assert.IsNotEqual(Guid.Empty, ss1.Id);
            Assert.IsEqual("My Favorite Symbols", ss1.Label);
            Assert.IsTrue(ss1.IsPrivate);
            Assert.IsEqual(Guid.Empty, ss1.Owner);
            Assert.IsNotNull(ss1.Categories);
            Assert.IsEqual(0, ss1.Categories.Count);
            Assert.IsNotNull(ss1.Symbols);
            Assert.IsEqual(0, ss1.Symbols.Count);

            Category c1 = new Category("Test Category A");
            Category c2 = new Category("Test Category B");
            ss1.AddCategory(c1);
            Assert.IsEqual(1, ss1.Categories.Count);
            Assert.IsEqual(c1, ss1.Categories[0]);

            ss1.AddCategory(c2);
            Assert.IsEqual(2, ss1.Categories.Count);
            Assert.IsEqual(c2, ss1.Categories[1]);

            List<Category> cList = new List<Category>();
            cList.Add( new Category("Test Category C") );
            cList.Add( new Category("Test Category D") );
            cList.Add( new Category("Test Category E") );
            ss1.AddCategoriesFrom(cList);
            Assert.IsEqual(5, ss1.Categories.Count);
            Assert.IsEqual(cList[0], ss1.Categories[2]);
            Assert.IsEqual(cList[1], ss1.Categories[3]);
            Assert.IsEqual(cList[2], ss1.Categories[4]);

            ss1.RemoveCategory(c1);
            Assert.IsEqual(4, ss1.Categories.Count);
            Assert.IsEqual(c2, ss1.Categories[0]);
            Assert.IsEqual(cList[0], ss1.Categories[1]);
            Assert.IsEqual(cList[1], ss1.Categories[2]);
            Assert.IsEqual(cList[2], ss1.Categories[3]);

            ss1.RemoveCategory(cList[2]);
            Assert.IsEqual(3, ss1.Categories.Count);
            Assert.IsEqual(c2, ss1.Categories[0]);
            Assert.IsEqual(cList[0], ss1.Categories[1]);
            Assert.IsEqual(cList[1], ss1.Categories[2]);

            ss1.RemoveCategory(cList[0]);
            Assert.IsEqual(2, ss1.Categories.Count);
            Assert.IsEqual(c2, ss1.Categories[0]);
            Assert.IsEqual(cList[1], ss1.Categories[1]);

            Category c3 = new Category(c2.Label);
            c3.Id = c2.Id;
            ss1.RemoveCategory(c3);
            Assert.IsEqual(1, ss1.Categories.Count);
            Assert.IsEqual(cList[1], ss1.Categories[0]);
        }

        [Test(4)]
        public void Test_SymbolSet_Symbols()
        {
            SymbolSet ss1 = new SymbolSet("My Favorite Symbols", true);
            Assert.IsNotEqual(Guid.Empty, ss1.Id);
            Assert.IsEqual("My Favorite Symbols", ss1.Label);
            Assert.IsTrue(ss1.IsPrivate);
            Assert.IsEqual(Guid.Empty, ss1.Owner);
            Assert.IsNotNull(ss1.Categories);
            Assert.IsEqual(0, ss1.Categories.Count);
            Assert.IsNotNull(ss1.Symbols);
            Assert.IsEqual(0, ss1.Symbols.Count);

            Image pic1 = new Bitmap("../../../Symbols/Alligator.jpg");
            Symbol s1 = Symbol.CreateSymbolWithLabelImage("Alligator", pic1);
            ss1.AddSymbol(s1);
            Assert.IsEqual(1, ss1.Symbols.Count);
            Assert.IsNotEqual(s1, ss1.Symbols[0]);
            Assert.IsEqual(s1.Label, ss1.Symbols[0].Label);
            Assert.IsEqual(s1.SymbolImage, ss1.Symbols[0].SymbolImage);
            Assert.IsEqual(ss1, ss1.Symbols[0].ContainingSymbolSet);

            Image pic2 = new Bitmap("../../../Symbols/Crab.jpg");
            Symbol s2 = Symbol.CreateSymbolWithLabelImage("Crab", pic2);
            ss1.AddSymbol(s2);
            Assert.IsEqual(2, ss1.Symbols.Count);
            Assert.IsNotEqual(s2, ss1.Symbols[1]);
            Assert.IsEqual(s2.Label, ss1.Symbols[1].Label);
            Assert.IsEqual(s2.SymbolImage, ss1.Symbols[1].SymbolImage);
            Assert.IsEqual(ss1, ss1.Symbols[1].ContainingSymbolSet);

            List<Symbol> sList = new List<Symbol>();
            sList.Add(Symbol.CopySymbol(s1));
            sList.Add(Symbol.CopySymbol(s2));
            sList.Add(Symbol.CopySymbol(s1));
            ss1.AddSymbolsFrom(sList);
            Assert.IsEqual(5, ss1.Symbols.Count);

            // Try removing s1, but nothing should happen because s1 is not in the list.  A unique copy of s1 is in the list
            ss1.RemoveSymbol(s1);
            Assert.IsEqual(5, ss1.Symbols.Count);

            ss1.RemoveSymbol(ss1.Symbols[0]);
            Assert.IsEqual(4, ss1.Symbols.Count);

            ss1.RemoveSymbol(ss1.Symbols[3]);
            Assert.IsEqual(3, ss1.Symbols.Count);

            ss1.RemoveSymbol(ss1.Symbols[1]);
            Assert.IsEqual(2, ss1.Symbols.Count);
        }

        private void Test_SaveAndLoad()
        {
            SymbolSet ss1 = new SymbolSet("My Favorite Symbols", true);

            ss1.AddSymbol(Symbol.CreateSymbolWithLabelImage("Alligator", new Bitmap("../../../Symbols/Alligator.jpg")));
            ss1.AddSymbol(Symbol.CreateSymbolWithLabelImage("Crab", new Bitmap("../../../Symbols/Crab.jpg")));
            ss1.AddSymbol(Symbol.CreateSymbolWithLabelImage("Dolphin", new Bitmap("../../../Symbols/Dolphin.jpg")));
            Assert.IsEqual(3, ss1.Symbols.Count);

            ss1.AddCategory(new Category("Test Category A"));
            ss1.AddCategory(new Category("Test Category B"));
            Assert.IsEqual(2, ss1.Categories.Count);

            // No exception should be thrown
            ss1.Save("TestData/ss1.xml");

            SymbolSet ss2 = new SymbolSet();
            ss2.Load("TestData/ss1.xml");
        }

#endif
        #endregion
    }
}
